hhkb
리버싱

악성코드_01_기드라를 이용한 PE 분석 기초

작성자 : Heehyeon Yoo|2025-12-03
# 리버싱# 악성코드# PE# 분석

기초 단계의 리버싱 실습

  • 기초 단계의 리버싱 실습으로 PE 악성코드 분석은 어떻게 접근해야 하는지 공유하기

정보보안기사 실기 기출문제
윈도우 PE(Portable Executable) 파일은 윈도우 7과 같이 NT계열 운영체제에서 실행 가능한 파일 포맷이다. PE 파일은 실행 코드, 데이터, 리소스 및 메타데이터를 포함하는 구조를 가지며, 일반적으로 .exe(executable), .dll(dynamic link library), .sys(driver) 확장자를 가진 파일들을 포함한다. 악성파일의 경우에도 윈도우 OS에서 실행되기 위해 PE포맷을 사용하는데, 악성코드 작성자는 PE파일을 난독화하거나, PE헤더와 섹션 정보를 변형하여 디버깅 및 분석을 어렵게 만든다. 

학습용 PE 제작

  • 실제 악성 행위는 없으며 로그와 메시지 박스로 ‘수상해 보이게끔’ 만든 것
// training_sample.c
#include <windows.h>
#include <stdio.h>
void write_log() {
    HANDLE hFile;
    DWORD written;
    const char *msg = "[TRAINING] sample log entry\r\n";
    // C:\temp가 없으면 실패할 수 있으니 미리 폴더 만들어 두기
    hFile = CreateFileA(
        "C:\\temp\\training_sample.log",
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    if (hFile == INVALID_HANDLE_VALUE) {
        return;
    }
    // 파일 끝으로 이동 후 로그 추가
    SetFilePointer(hFile, 0, NULL, FILE_END);
    WriteFile(hFile, msg, (DWORD)lstrlenA(msg), &written, NULL);
    CloseHandle(hFile);
}
int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     lpCmdLine,
    int       nCmdShow
) {
    // 콘솔 생성해서 디버깅 메시지 확인 가능하게
    AllocConsole();
    FILE *fp;
    freopen_s(&fp, "CONOUT$", "w", stdout);
    printf("[TRAINING] sample started.\n");
    DWORD start = GetTickCount();
    write_log();
    MessageBoxA(
        NULL,
        "This is a harmless training sample.\nYou can analyze this in Ghidra.",
        "Training Sample",
        MB_OK | MB_ICONINFORMATION
    );
    // 조금 대기
    Sleep(5000);
    printf("[TRAINING] exiting after %lu ms.\n", GetTickCount() - start);
    FreeConsole();
    return 0;
}
  • 빌드 후 실행하면 콘솔과 경고창이 나타나고, Temp 폴더에 log를 남김
BlockNote image BlockNote image
  • 기드라로 불러오기 → PE64 자동 감지 → 자동 분석
BlockNote image BlockNote image

기드라 분석 시작

1. 엔트리 포인트 확인

BlockNote image
  • 일반적으로 기드라가 자동으로 엔트리 포인트를 잡아주지만, Symbol Tree→Functions→Entry를 찾아서도 엔트리 포인트 확인 가능
  • 악성 코드는 엔트리 포인트를 숨기기도 함
    • 대표적인 기법은 패킹(UPX/Themida 등 압축, 암호화) → 더미 코드가 엔트리로 표시
    • 핵심 로직을 DLL로 분리하여 exe는 빈 껍데기로, 실 행위는 dll에서 실행하는 경우도 엔트리 포인트 식별 어려움
    • 쓰레기 코드와 JMP(점프)로 복잡하게 해놓은 Obfuscated Entry Block도 있음
    • 실행 중 메모리에 실제 코드를 로드한 후 시행하는 Dynamic Code Unpack도 엔트리 포인트 식별 어려움
      • 분석이 가장 어려운 것은 Shellcode Loader로 Entry에서 쉘코드 호출만 하고 프로그램이 즉시 종료되는 경우

2. Import Table 분석

BlockNote image
  • Import Table : 프로그램이 실행될 때 외부 DLL에 있는 함수(윈도우 API)를 가져오는 목록
    • 악성코드 분석의 가장 첫 단계
  • Kernel32.dll, User32.dll의 API 목록에서 주요한 API 확인
    • CreateFileA : 파일 생성/열기
    • WriteFile : 파일 기록(로그나 키로깅 가능)
    • SetFilePointer : 파일 끝에 이어쓰기 가능
    • Sleep : 보안 솔루션 회피를 위해 일정 시간 잠복하거나 타이밍 조절
    • MessageBoxA : 사용자 인터페이스 조작(여기서는 가짜 경고창)

3. 디컴파일하여 로직 이해하기

BlockNote image
  • 엔트리에서 호출하는 두 개의 함수 확인
    • FUN_140001808은 보안 쿠키 초기화하는 함수
    • FUN_1400012c4가 CRT 초기화 함수
    • 내부 함수들 확인하며 2번(ImportTable)에서 확인했던 API 사용한 함수 찾기
    • 대부분 WinMain에 있음
    • iVar3 = FUN_140001070(); 발견, 코드 분석
BlockNote image
void FUN_140001070(void)
{
    DWORD DVar1;
    DWORD DVar2;
    FILE *_OldFile;
    HANDLE hFile;
    char *pcVar3;
    undefined *puVar4;
    undefined8 uVar5;
    undefined1 auStackY_68 [32];
    FILE *local_28;
    DWORD local_20 [2];
    ulonglong local_18;
              
    // 스택 쿠키: 런타임 보안용 → 악성 행위 아님
    local_18 = DAT_140005000 ^ (ulonglong)auStackY_68;
              
    // 콘솔 창을 생성 (UI 동작)
    AllocConsole();
              
    // stdout 핸들 가져오기 → 콘솔로 리다이렉션 준비
    _OldFile = (FILE *)__acrt_iob_func(1);
    puVar4 = &DAT_1400032ec;
    pcVar3 = "CONOUT$";
              
    // 출력 스트림을 콘솔로 연결
    freopen_s(&local_28,"CONOUT$","w",_OldFile);
              
    // 시작 메시지 출력
    FUN_140001010("[TRAINING] sample started.\n",pcVar3,puVar4,_OldFile);
              
    // 실행 시작 시간 기록
    DVar1 = GetTickCount();
              
    // 로그 파일 생성/열기
    hFile = CreateFileA("C:\\temp\\training_sample.log"
            ,0x40000000,
            1,
            (LPSECURITY_ATTRIBUTES)0x0,
            4,
            0x80
            ,(HANDLE)0x0);
                        
    if (hFile != (HANDLE)0xffffffffffffffff) {
        // 파일 끝으로 이동 → Append 방식
        SetFilePointer(hFile,0,(PLONG)0x0,2);
                
        // 로그 텍스트 길이 구하기
        DVar2 = lstrlenA("[TRAINING] sample log entry\r\n");
                
        // 로그 내용을 파일에 기록
        WriteFile(hFile,"[TRAINING] sample log entry\r\n",DVar2,local_20,(LPOVERLAPPED)0x0);
                
        // 파일 핸들 닫기
        CloseHandle(hFile);
    }
              
    // 메시지 박스 표시 (UI 동작)
    uVar5 = 0x40;
    pcVar3 = "Training Sample";
    MessageBoxA((HWND)0x0,
        "This is a harmless training sample.\nYou can analyze this in Ghidra.",
        "Training Sample",
        0x40);
            	  
    // 대기 (행위 지연/탐지 우회 가능)
    Sleep(5000);
    DVar2 = GetTickCount();
            
    // 종료 시간 측정
    FUN_140001010("[TRAINING] exiting after %lu ms.\n",(ulonglong)(DVar2 - DVar1),pcVar3,uVar5);
            
    // 콘솔 닫기
    FreeConsole();
              
    // 스택 쿠키 검증 (정상 종료 확인)
    FUN_1400011c0(local_18 ^ (ulonglong)auStackY_68);
    return;
    }

분석 보고서 샘플